package com.yb.shiro.server.common;

import com.alibaba.fastjson.JSON;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.lang3.StringUtils;

import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Base64;
import java.util.Date;
import java.util.Map;
import java.util.Objects;

/**
 * Jwt生成和解析工具
 * 这个使用的设定好的秘钥和加密方式
 *
 * @author biaoyang
 */
public class JwtTool {
    private static final String AUTHORIZATION_HEADER = "Bearer ";
    private static final String COMMA = ".";
    private static final Integer THREE = 3;

    /**
     * 获取jwt唯一身份识别码jti
     *
     * @return
     */
    public static String createJti() {
        return String.valueOf(System.nanoTime());
    }

    /**
     * 通过加密算法和秘钥生成加密jwt的token的Key
     * 这个是使用默认秘钥的加密方式的方法生成key
     *
     * @return
     */
    private static Key getKey() {
        byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(JwtDic.BASE64_ENCODE_SECRET);
        return new SecretKeySpec(apiKeySecretBytes, SignatureAlgorithm.HS512.getJcaName());
    }

    /**
     * 验证jwt的token的签名,是否能够验签通过
     *
     * @param jsonWebToken
     * @return
     */
    public static boolean verifySignature(String jsonWebToken) {
        return Jwts.parser()
                .setSigningKey(getKey())
                .isSigned(jsonWebToken);
    }

    /**
     * 生成accessToken
     * 使用了通过秘钥加密的key,再用一个加密算法加密key形成token的签名
     * 一般测试的时候用的是一个字符串作为秘钥加密生成签名,而没有用key
     *
     * @param user      没有敏感信息的用户信息包装类对象
     *                  (这个是直接放在只经过base64编码的荷载中,有必要的时候可以加密只有base64编码的token,对称或非对称加密)
     * @param ttlMillis accessToken的过期时间(毫秒)
     * @return
     */
    public static String createAccessToken(LoginUser user, long ttlMillis) {
        //添加构成JWT的参数
        JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JWT")
                //设置授权者
                .setIssuer(JwtDic.ISS)
                //设置鉴权者
                .setAudience(JwtDic.AUD)
                //添加jwt的id,也就是jti
                .setId(user.getJti())
                //装填用户信息到荷载
                .addClaims((Map<String, Object>) JSON.toJSON(user))
                //设置subject
                .setSubject(user.getUsername())
                .signWith(SignatureAlgorithm.HS512, getKey());
        //添加Token过期时间
        if (ttlMillis > 0) {
            long expMillis = System.currentTimeMillis() + ttlMillis;
            Date exp = new Date(expMillis);
            builder.setExpiration(exp)
                    .setNotBefore(new Date(System.currentTimeMillis()));
        }
        //生成JWT并为其加上前缀Bearer
        return JwtDic.HEADERS_VALUE_PREFIX + builder.compact();
    }

    /**
     * 创建刷新token
     * 使用了通过秘钥加密的key,再用一个加密算法加密key形成token的签名
     * 一般测试的时候用的是一个字符串作为秘钥加密生成签名,而没有用key
     *
     * @param username  用户名
     * @param ttlMillis refreshToken的过期时间(毫秒)
     * @return
     */
    public static String createRefreshToken(String username, long ttlMillis) {
        //添加构成JWT的参数
        JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JWT")
                //设置授权者
                .setIssuer(JwtDic.ISS)
                //设置鉴权者
                .setAudience(JwtDic.AUD)
                .claim("scope", "REFRESH")
                .setSubject(username)
                .signWith(SignatureAlgorithm.HS256, getKey());
        //添加Token过期时间
        if (ttlMillis > 0) {
            long expMillis = System.currentTimeMillis() + ttlMillis;
            Date exp = new Date(expMillis);
            builder.setExpiration(exp).setNotBefore(new Date(System.currentTimeMillis()));
        }
        //生成JWT
        return builder.compact();
    }

    /**
     * 校验token的合法性,并且获取荷载信息
     * 因为设定用的是LoginUser来封装数据到荷载,解析也是如此,如果解析荷载为空,就判定是token合法性认证失败
     * 设定荷载里必须要有一定的不敏感的用户信息,用来提供给业务用,或者前端展示用
     *
     * @param jwtWebToken
     * @return
     */
    public static LoginUser checkAndGetLoginUser(String jwtWebToken) {
        //判断token的合法性
        if (StringUtils.isNotBlank(jwtWebToken) && jwtWebToken.startsWith(AUTHORIZATION_HEADER)) {
            //去掉头部的Bearer
            jwtWebToken = jwtWebToken.replaceFirst(AUTHORIZATION_HEADER, "");
            //验证签名并对jwt的token进行切割判断
            //------注意坑:\\.点转义放在变量里,会导致转义失效,而导致字符串用点切割失败,所以这魔法就放着吧(可以替换成其他字符再处理)
            if (verifySignature(jwtWebToken) && jwtWebToken.contains(COMMA) && jwtWebToken.split("\\.").length == THREE) {
                //获取荷载内容,实测用DatatypeConverter.parseBase64Binary解析,
                //会导致解析出的荷载是没有后面那个大括号的,会导致解析成LoginUser失败
                String userInfo = new String(Base64.getUrlDecoder().decode(jwtWebToken.split("\\.")[1]));
                //解析荷载(封装的时候也要是JSON转的对象,才能反过来解析出来)
                if (StringUtils.isNotBlank(userInfo)) {
                    //解析荷载为用户信息(封装数据的时候通过JSON字符串化)
                    LoginUser loginUser = JSON.parseObject(userInfo, LoginUser.class);
                    //返回封装在荷载的LoginUser信息
                    return Objects.nonNull(loginUser) ? loginUser : null;
                }
            }
        }
        //不满足条件返回null
        return null;
    }

}
